#include "Rational.h"

#include <algorithm>
#include <cstdlib>
#include <sstream>
#include <iostream>

Rational::Rational() :
    numerator( 0 ),
    denominator( 1 ),
    _displayMode( Vulgar )
{
}

Rational::Rational( int numerator, int denominator ) :
    numerator( numerator ),
    denominator( denominator ),
    _displayMode( Vulgar )
{
}

Rational::Rational( int wholeNumber ) :
    numerator( wholeNumber ),
    denominator( 1 ),
    _displayMode( Vulgar )
{
}

Rational::Rational( const Rational &other ) :
    numerator( other.numerator ),
    denominator( other.denominator ),
    _displayMode( other.displayMode() )
{
}

void Rational::setDisplayMode( DisplayMode mode )
{
    _displayMode = mode;
}

Rational::DisplayMode Rational::displayMode() const
{
    return _displayMode;
}

std::string Rational::toVulgarString() const
{
    std::stringstream stream;
    
    stream << numerator;
    if( denominator != 1 && numerator != 0 )
	    stream << "/" << denominator;
    
    return stream.str();
}

std::string Rational::toMixedString() const
{
    int wholeNumber = numerator / denominator;
    
    if( wholeNumber == 0 ) 
    {
	    return toVulgarString();
    }
    else
    {
        int remainder = numerator % denominator;
        
        std::stringstream stream;
        stream << wholeNumber;
        
        if( remainder != 0 ) 
            stream << " " << abs(remainder) << "/" << denominator;
        
        return stream.str();
    }
}

Rational Rational::reduced() const
{
    Rational temp = *this;
    temp.reduce();
    return temp;
}

void Rational::reduce()
{
    int divisor = gcd( numerator, denominator );
    numerator /= divisor;
    denominator /= divisor;
}

Rational Rational::reciprocal() const
{
    return Rational( denominator, numerator );
}

void Rational::reciprocate()
{
    std::swap( numerator, denominator );
}

int Rational::gcd( int first, int second )
{
    first = abs( first );
    second = abs( second );

    if ( second > first ) 
        std::swap( first, second ); 

    int temp;
    while ( second > 0 ) 
    {
        temp = second;
        second = first % second;
        first = temp;
    }
    
    return first;
}

int Rational::lcm( int first, int second )
{
    return ( first / gcd(first, second) ) * second;
}

bool Rational::operator ==( const Rational &other ) const
{
    // Apply the rule a/b = c/d iff ad = bc
    return numerator   * other.denominator == 
           denominator * other.numerator;
           
}

bool Rational::operator !=( const Rational &other ) const
{
    return !( *this == other );
}

bool Rational::operator >( const Rational &other ) const
{
    Rational temp1 = *this;
    Rational temp2 = other;
    _equalizeDenominators( temp1, temp2 );
    
    return temp1.numerator > temp2.numerator;
}

bool Rational::operator <( const Rational &other ) const
{
    Rational temp1 = *this;
    Rational temp2 = other;
    _equalizeDenominators( temp1, temp2 );
    
    return temp1.numerator < temp2.numerator;
}

bool Rational::operator >=( const Rational &other ) const
{
    return *this > other || *this == other;
}

bool Rational::operator <=( const Rational &other ) const
{
    return *this < other || *this == other;
}

Rational Rational::operator +( const Rational &other ) const
{
    Rational temp1( *this );
    Rational temp2( other );
    
    _equalizeDenominators( temp1, temp2 );
    
    temp1.numerator += temp2.numerator;
    
    return temp1.reduced();
}

Rational Rational::operator -( const Rational &other ) const
{    
    Rational temp1( *this );
    Rational temp2( other );
    
    _equalizeDenominators( temp1, temp2 );
    
    temp1.numerator -= temp2.numerator;
 
    return temp1.reduced();
}

Rational Rational::operator *( const Rational &other ) const
{
    return Rational( other.numerator * numerator, 
                     other.denominator * denominator ).reduced();
}

Rational Rational::operator /( const Rational &other ) const
{
    return Rational( numerator * other.denominator,
                     denominator * other.numerator ).reduced();
}
        
Rational &Rational::operator +=( const Rational &other )
{
    Rational temp = other;
    _equalizeDenominators( *this, temp );
    numerator += other.numerator;
    reduce();
    
    return *this;
}

Rational &Rational::operator -=( const Rational &other )
{    
    Rational temp = other;
    _equalizeDenominators( *this, temp );
    numerator -= other.numerator;
    reduce();
    
    return *this;
}

Rational &Rational::operator *=( const Rational &other )
{
    Rational temp = *this * other;
    numerator = temp.numerator;
    denominator = temp.denominator;
    reduce();
    
    return *this;
}

Rational &Rational::operator /=( const Rational &other )
{
    Rational temp = *this / other;
    numerator = temp.numerator;
    denominator = temp.denominator;
    reduce();
    
    return *this;
}

void Rational::_equalizeDenominators( Rational &r1, Rational &r2 ) const
{
    // Save off the denominators since we'll be changing them but want
    // to keep mulitplying by their original values.
    int denom1 = r1.denominator;
    int denom2 = r2.denominator;
    
    r1.numerator   *= denom2;
    r1.denominator *= denom2;
    r2.numerator   *= denom1;
    r2.denominator *= denom1;
}

std::ostream &operator <<( std::ostream &os, const Rational &r )
{
    if( r.displayMode() == Rational::Vulgar )
        os << r.toVulgarString();
    else
        os << r.toMixedString();    
        
    return os;
}
        
